
package app;

import java.awt.Point;

/**
 * Klasa odpowiedzialna za sterowanie auta. Na podstawie sztucznej inteligencji
 * określa nowe wartości przyspieszenia i skrętu auta.
 * @author CygiDawid
 */
public class AIController {
    private final double DEG_CALC = 180d / Math.PI;
    private final Car _car;
    private Track _actualTrack;
    private int _trackWidth;
    private final Key[] _keys = new Key[] { new Key(), new Key(), new Key(), new Key() };
    private int _pointId = 0;
    private Point _point;
    
    
    private final MyFuzzyLogic _logic;
    
    
    /**
     * Konstruktor parametryczny
     * @param pictureName Nazwa pliku z obrazkiem auta
     */
    public AIController(String pictureName) {
        _car = new Car(pictureName, _keys);
        _logic = new MyFuzzyLogic();
    }
    
    
    /**
     * Pobiera auto, którym steruje konkretny obiekt kontrolera
     * @return Obiekt auta
     */
    public Car getCar() {
        return _car;
    }
    
    /**
     * Ustawia trasę, po której ma jeździć auto
     * @param track Trasa
     * @param width Szerokość trasy
     */
    public void setTrack(Track track, int width) {
        double carMidX = _car.getWidth() / 2;
        double carMidY = _car.getHeight() / 2;
        _trackWidth = (width / 3 * 2) + (int)(Math.sqrt(carMidX * carMidX + carMidY * carMidY) / 2);
        _trackWidth *= _trackWidth;
        _actualTrack = track;
        _pointId = 0;
        _point = _actualTrack.getPoint(_pointId);
    }
    
    /**
     * Odświeża wartości przyspieszenia i skrętu auta
     */
    public void correct() {
        double xDistance = _point.x - (_car.getX() + (_car.getWidth() / 2));
        double yDistance = _point.y - (_car.getY() + (_car.getHeight() / 2));
        double distance = xDistance * xDistance + yDistance * yDistance;
        
        if (distance <= _trackWidth) {
            _pointId = (_pointId + 1) % _actualTrack.getPointsCount();
            _point = _actualTrack.getPoint(_pointId);
            correct();
        }
        
        Point carPoint = _car.getMidPoint();
        
        double deltaX = carPoint.x - _point.x;
        double deltaY = carPoint.y - _point.y;
        double angle = -(Math.atan2(deltaX, deltaY) * DEG_CALC);
        
        double angleDiff = calcAngle(angle, _car.getAngle());
        double absAngleDiff = Math.abs(angleDiff);
        
        rotate(_logic.getSteer(angleDiff));
        move(_logic.getAcceleration(absAngleDiff, _car.getSpeed()));
    }
    
    /**
     * Oblicza kąt o jaki auto musi się obrócić, aby skierować się w stronę
     * następnego punktu; Uwzględnia stronę: prawo - lewo
     * @param pointAngle Kąt pomiędzy autem, a następnym punktem
     * @param carAngle Kąt obrócenia auta
     * @return Kąt do obrócenia auta
     */
    private double calcAngle(double pointAngle, double carAngle) {
        double angleDiff;
        double dir = 1d;
        if (pointAngle < carAngle) {
            dir = -1d;
        }
        
        if (Math.signum(pointAngle) == Math.signum(carAngle)) {
            angleDiff = Math.abs(pointAngle - carAngle);
        }
        else {
            angleDiff = Math.abs(pointAngle) + Math.abs(carAngle);
            if (angleDiff > 180d) {
                angleDiff = 360d - angleDiff;
                dir = -dir;
            }
        }
        
        return (angleDiff * dir);
    }
    
    /**
     * Ustawia auto w stan początkowy
     */
    public void reset() {
        move(0d);
        rotate(0d);
    }
    
    /**
     * Ustawia moc poruszania autem
     * @param power Moc poruszania
     */
    private void move(double power) {
        if (power >= 0) {
            _keys[0].setPower(power);
            _keys[1].resetPower();
        }
        else {
            _keys[0].resetPower();
            _keys[1].setPower(-power);
        }
    }
    
    /**
     * Ustawia moc skrętu autem
     * @param power Moc skrętu
     */
    private void rotate(double power) {
        if (power <= 0) {
            _keys[2].setPower(-power);
            _keys[3].resetPower();
        }
        else {
            _keys[2].resetPower();
            _keys[3].setPower(power);
        }
    }
}
